﻿/*******************************************************************************
 * @file usart.c
 * @brief 串口模块
 *******************************************************************************
*/

#include <string.h>
#include <stdlib.h>
#include "usart.h"
#include "gd32f4xx.h"
#include "systick.h"

/* 串口实例表 */
static UsartType_t *s_usart_instance_table[USART_INSTANCE_MAX] = { 0 };


static void _UsartConfig(const UsartType_t *instance, const UsartContig_t *config)
{
    (void)instance;

    /* 使能时钟 */
    rcu_periph_clock_enable(RCU_USART0);

    /* 串口配置 */
    usart_deinit(instance->usartx);
    usart_baudrate_set(instance->usartx, config->baudrate);
    usart_parity_config(instance->usartx, config->parity);
    usart_stop_bit_set(instance->usartx, config->stopbits);
    usart_word_length_set(instance->usartx, config->databits);
    usart_receive_config(instance->usartx, USART_RECEIVE_ENABLE);
    usart_transmit_config(instance->usartx, USART_TRANSMIT_ENABLE);

    /* 使能串口 */
    usart_enable(instance->usartx);

    /* 使能串口中断 */
    usart_interrupt_enable(instance->usartx, USART_INT_IDLE);
    nvic_irq_enable(USART0_IRQn, 0, 0);
}

static void _UsartDmaConfig(const UsartType_t *instance, uint32_t dmax, dma_channel_enum chx)
{
    dma_single_data_parameter_struct dma_init_struct;
    /* enable DMA1 */
    rcu_periph_clock_enable(RCU_DMA1);

    /* deinitialize DMA channel2 (USART0 RX) */
    uint32_t mem_addr = (uint32_t)instance->recive_buffer[instance->recive_wirt_ptr];
    dma_deinit(dmax, chx);
    usart_flag_clear(instance->usartx, USART_FLAG_RBNE);
    usart_dma_receive_config(instance->usartx, USART_RECEIVE_DMA_ENABLE);
    dma_init_struct.direction = DMA_PERIPH_TO_MEMORY;
    dma_init_struct.memory0_addr = mem_addr;
    dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_init_struct.number = instance->recive_buffer_size;
    dma_init_struct.periph_addr = (uint32_t)&USART_DATA(instance->usartx);
    dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
    dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
    dma_single_data_mode_init(dmax, chx, &dma_init_struct);
    /* configure DMA mode */
    dma_circulation_disable(dmax, chx);
    dma_channel_subperipheral_select(dmax, chx, DMA_SUBPERI4);
    /* enable DMA channel2 */
    dma_channel_enable(dmax, chx);
}

/**
 * @brief 构造串口实例
 * @param instance 实例
 * @param usartx 串口外设
 * @param recive_buffer 接收缓冲区指针
 * @param recive_buffer_size 缓冲区大小
 * @return -1: 错误, 0: 成功, 1: 串口实例已存在, 2: 内存分配失败
 */
int Usart_Constructor(UsartType_t *instance, uint32_t usartx, uint32_t recive_buffer_size)
{
    if(!instance) return -1;

    /* 注册串口实例 */
    if(s_usart_instance_table[0]) return 1;
    instance->usartx             = usartx;
    instance->recive_buffer[0]   = malloc(recive_buffer_size);
    if(!instance->recive_buffer[0]) return 2;
    instance->recive_buffer[1]   = malloc(recive_buffer_size);
    if(!instance->recive_buffer[1]) return 2;
    instance->recive_buffer_size = recive_buffer_size;
    s_usart_instance_table[0]    = instance;

    return 0;
}

/**
 * @brief 串口初始化函数
 * @param[in] instance 串口实例
 * @return -1: 错误, 0: 成功
 */
int Usart0_Init(const UsartType_t *instance, const UsartContig_t *config)
{
    if(!instance || !config) return -1;

    /* 配置GPIO */
    rcu_periph_clock_enable(RCU_GPIOA);

    gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_9);
    gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_10);
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_9);
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_10);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_10);

    /* 配置串口 */
    _UsartConfig(instance, config);

    /* 配置DMA */
    _UsartDmaConfig(instance, DMA1, DMA_CH2);

    /* USART DMA enable for transmission and reception */
    usart_dma_receive_config(instance->usartx, USART_RECEIVE_DMA_ENABLE);
    
    return 0;
}

/**
 * @brief 发送数据
 * @param[in] instance 串口实例 
 * @param[in] data 数据
 * @param[in] size 数据大小
 * @param[in] timeout 超时时间, 单位: ms
 * @return -1: 错误, 0: 发送成功, 1: 发送超时
 */
int USART_Send(const UsartType_t *instance, uint8_t *data, uint32_t size, uint32_t timeout)
{
    if(!instance ||!data) return -1;

    uint32_t oldtick = systick_get_tick();
    while(size--)
    {
        usart_data_transmit(instance->usartx, *data++);
        while(RESET == usart_flag_get(instance->usartx, USART_FLAG_TBE))
            if(systick_get_tick() - oldtick > timeout) return 1;
    }

    return 0;
}

/**
 * @brief 读取数据
 * @param[in] instance 串口实例 
 * @param[in] data 数据暂存区
 * @param[out] size 数据大小
 * @return -1: 错误, 0: 读取成功, 1: 无数据
 */
int USART_Read(UsartType_t *instance, uint8_t *data, uint32_t *size)
{
    if(!instance || !data || !size) return -1;

    if(instance->recive_full_ptr == 0) return 1;

    __disable_irq();
    uint8_t *ptr = instance->recive_buffer[instance->recive_full_ptr - 1];
    instance->recive_full_ptr = 0;
    *size = instance->recive_len;
    __enable_irq();

    memcpy(data, ptr, *size);
    memset(ptr, 0, *size);

    return 0;
}

/**
 * @brief 重写串口空闲中断处理函数
 */
void USART_IdleCallback(uint32_t usartx)
{
    UsartType_t *instance = NULL;
    uint32_t len = 0;

    if(usartx == USART0)
    {
        instance = s_usart_instance_table[0];
        if(!instance) return;

        /* 关闭DMA */
        dma_channel_disable(DMA1, DMA_CH2);

        /* 获取数据长度 */
        len = instance->recive_buffer_size - dma_transfer_number_get(DMA1, DMA_CH2);
        instance->recive_len       = len;
        instance->recive_full_ptr  = instance->recive_wirt_ptr + 1;
        instance->recive_wirt_ptr ^= 1;

        /* 重新配置DMA */
        uint32_t new_addr = (uint32_t)instance->recive_buffer[instance->recive_wirt_ptr];
        dma_memory_address_config(DMA1, DMA_CH2, DMA_MEMORY_0, new_addr);
        dma_transfer_number_config(DMA1, DMA_CH2, instance->recive_buffer_size);
        dma_flag_clear(DMA1, DMA_CH2, DMA_FLAG_FTF);
        dma_channel_enable(DMA1, DMA_CH2);
    }
    else if(usartx == USART1)
    {
        instance = s_usart_instance_table[1];
    }
}
